package com.iot.auth.filter;

import java.io.IOException;
import java.text.ParseException;
import java.util.Date;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.AccountExpiredException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.util.ObjectUtils;

import com.alibaba.fastjson.JSONObject;
import com.iot.common.core.constant.RedisConstants;
import com.iot.common.core.constant.SecurityConstants;
import com.iot.common.core.util.Md5Util;
import com.iot.common.core.util.R;
import com.iot.common.core.util.SpringContextHolder;
import com.iot.common.core.util.WebUtils;
import com.iot.common.log.event.SysLogEvent;
import com.iot.common.log.util.LogTypeEnum;
import com.iot.common.log.util.SysLogUtils;
import com.iot.common.security.domain.SimpleUser;
import com.iot.manage.api.domain.SysLog;
import com.jfinal.kit.StrKit;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSVerifier;
import com.nimbusds.jose.crypto.MACVerifier;
import com.nimbusds.jwt.SignedJWT;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class SecurityAuthorizationFilter extends BasicAuthenticationFilter {

	private StringRedisTemplate redisTemplate;

	public SecurityAuthorizationFilter(AuthenticationManager authenticationManager, StringRedisTemplate redisTemplate) {
		super(authenticationManager);
		this.redisTemplate = redisTemplate;
	}

	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
			throws IOException, ServletException {

		String headerToken = request.getHeader(SecurityConstants.TOKEN_HEADER);
		if (StrKit.isBlank(headerToken) || !headerToken.startsWith(SecurityConstants.TOKEN_PREFIX)) {
			chain.doFilter(request, response);
			return;
		}

		try {
			UsernamePasswordAuthenticationToken authentication = getAuthentication(headerToken);
			SecurityContextHolder.getContext().setAuthentication(authentication);
			onSuccessfulAuthentication(request, response, authentication);
			chain.doFilter(request, response);
		} catch (Exception e) {
			onUnsuccessfulAuthentication(request, response, new AccountExpiredException(e.getMessage()));
		}
	}
	
	@Override
	protected void onSuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
			Authentication authResult) throws IOException {
		super.onSuccessfulAuthentication(request, response, authResult);
		log.info("=============Token验证通过======");
	}

	@Override
	protected void onUnsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
			AuthenticationException failed) throws IOException {
		SysLog sysLog = SysLogUtils.getSysLog();
		sysLog.setTitle("Token校验");
		Long startTime = System.currentTimeMillis();
		sysLog.setType(LogTypeEnum.ERROR.getType());

		log.error("Token校验失败:{}", failed.getMessage());
		WebUtils.renderJson(response, R.failed(HttpServletResponse.SC_UNAUTHORIZED, "Token已过期.", failed.getMessage()));

		// 日志记录
		sysLog.setContent(failed.getMessage());
		Long endTime = System.currentTimeMillis();
		sysLog.setTime(endTime - startTime);
		SpringContextHolder.publishEvent(new SysLogEvent(sysLog));
	}

	/**
	 * getAuthentication
	 * 
	 * @author love
	 * @Description
	 * @Date 2021年8月14日 - 下午12:43:50
	 * @param tokenHeader
	 * @return
	 * @throws ParseException
	 * @throws JOSEException
	 */
	private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader)
			throws ParseException, JOSEException {

		String token = tokenHeader.replace(SecurityConstants.TOKEN_PREFIX, "");
		SignedJWT jwt = SignedJWT.parse(token);
		JWSVerifier verifier = new MACVerifier(SecurityConstants.TOKEN_SECRET);
		if (!jwt.verify(verifier)) {
			throw new AccountExpiredException(SecurityConstants.TOKEN_INVALID);
		}

		// 校验超时
		Date expirationTime = jwt.getJWTClaimsSet().getExpirationTime();
		if (new Date().after(expirationTime)) {
			throw new AccountExpiredException(SecurityConstants.TOKEN_EXPIRE);
		}

		// 获取载体中的数据
		Object account = jwt.getJWTClaimsSet().getClaim("payload");
		if (account != null) {
			SimpleUser user = JSONObject.parseObject(account.toString(), SimpleUser.class);

			String keyPrefix = RedisConstants.TOKEN_KEY_PREFIX + user.getId() + ":";
			String keySuffix = Md5Util.getMD5(token.getBytes());
			String key = keyPrefix + keySuffix;

			String redisToken = redisTemplate.opsForValue().get(key);
			if (ObjectUtils.isEmpty(redisToken)) {
				throw new AccountExpiredException(SecurityConstants.TOKEN_CANCEL);
			}

			if (!redisToken.equals(token)) {
				throw new AccountExpiredException(SecurityConstants.TOKEN_INVALID);
			}

			String authKey = key + RedisConstants.AUTH_KEY;
			String redisAuth = redisTemplate.opsForValue().get(authKey);
			log.info("redisAuth:{}", redisAuth);
			return new UsernamePasswordAuthenticationToken(user.getUsername(), null, user.getAuthorities());
		}
		return null;
	}

}
